/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package util;

import entities.TestCase;
import entities.User;
import entities.Run;
import entities.Problem;
import entities.Submission;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Vector;
import java.util.Date;

/**
 *
 * @author Niquefa
 */
public class CompileAndExecuteAllTestCase
{

    public User getCompetitor() {
        return competitor;
    }

    public boolean isCompilationSuccessfull() {
        return compilationSuccessfull;
    }

    public String getLanguaje() {
        return languaje;
    }

    public Problem getProblem() {
        return problem;
    }

    public Vector<Run> getRuns() {
        return runs;
    }

    public String getStatus() {
        return status;
    }

    public Submission getSubmission() {
        return submission;
    }

    public Vector<TestCase> getTestCases() {
        return TestCases;
    }

    public String getVerdict() {
        return verdict;
    }
    private Problem problem;
    private Vector<TestCase> TestCases;
    private String languaje;
    private String sourceCode;
    private User competitor;
    private Submission submission;
    private String verdict = EnumStatus.NOT_COMPILED.value();
    private String status = EnumStatus.NOT_COMPILED.value();
    private boolean compilationSuccessfull = false;

    private String compilationOutput = EnumStatus.NOT_COMPILED.value();
    private String executionOutput = EnumStatus.NOT_COMPILED.value();

    private Vector<Run> runs = new Vector<Run>();

    private CompileAndExecuteAllTestCase(){}

    public CompileAndExecuteAllTestCase( Problem problem , Vector<TestCase> TestCases, User competitor , Submission submission )
    {
        this.deleteFiles();
        this.problem = problem;
        this.TestCases = TestCases;
        this.languaje = submission.getLanguage();
        this.sourceCode = submission.getCode();
        this.competitor = competitor;
        if( createSourceFile() )
        {
            compilationSuccessfull = this.compile();
            if( compilationSuccessfull )
            {
                this.verdict = EnumVerdicts.CE.value();
                this.status = EnumStatus.UNSUCCESSFULLY_COMPILED.value();
            }
            else
            {
                this.verdict = EnumStatus.SUCCESSFULLY_COMPILED.value();
                this.status = EnumStatus.SUCCESSFULLY_COMPILED.value();
            }
        }
        else
        {
            this.status = EnumStatus.SOURCE_CODE_ERROR.value();
        }
    }
    public boolean isSuccessfullyCompiled()
    {
        return this.compilationSuccessfull;
    }
    public static void seeWorkSpaceDir()throws Exception
    {
        Runtime rt = Runtime.getRuntime();
        Process p=rt.exec("cmd.exe /C dir");

        InputStream es = p.getInputStream();
        BufferedReader br = new BufferedReader (new InputStreamReader (es));
        String aux;
        String salida = "";
        while ( (aux = br.readLine())!=null )
            salida += aux+"\n";

        System.out.println("(seeWorkSpaceDir)SALIDA \n"+salida);
    }
    public static void killProcess( String processName )throws Exception
    {
        //TO DO
        if( processName.indexOf("java.exe") >= 0)return;
        Runtime rt = Runtime.getRuntime();
        Process p=rt.exec("cmd.exe /C taskkill /F /IM "+processName+"");
        InputStream es = p.getInputStream();
        BufferedReader br = new BufferedReader (new InputStreamReader (es));
        String aux;
        String salida = "";
        while ( (aux = br.readLine())!=null )
            salida += aux+"\n";

        //System.out.println("(killProcess)SALIDA \n"+salida);
    }
    public static void main( String args[] )throws Exception
    {
        //seeWorkSpaceDir();
        String javaCode = "import java.io.*;\n"+
                    "import java.util.StringTokenizer;\n"+
                    "public class Main \n"+
                    "{\n"+
                    "        public static void main(String[] args)throws Exception\n"+
                    "        {\n"+
                    "                for(int i = 0; i < 9; ++i ){++i;--i;++i;--i;} \n"+
                    "                StringTokenizer ts = new StringTokenizer((new BufferedReader( new InputStreamReader( System.in ) )).readLine());\n"+
                    "                System.out.println(Integer.parseInt(ts.nextToken())+Integer.parseInt(ts.nextToken())+Integer.parseInt(ts.nextToken())); \n"+
                    "        }\n"+
                    "}";

        String code = "#include <iostream>\n"+
                            "using namespace std;\n"+
                            "int main()\n"+
                            "{for(int i = 0; i < 8; ++i ){++i;--i;++i;--i;if(i%10000000 != 0 )cout << i << endl;} \n"+
                               "\tint a , b , c;\n"+
                               "\tcin >> a >> b >> c;\n"+
                               "\tcout << a + b + c <<endl;int cc = 3.3;cc >?= 32;\n"+
                               "\treturn 0;\n"+
                            "}\n";
        Problem problem = new Problem();
        problem.setName("Suma 3 numeros");
        problem.setDescription("Lea una linea con tres números y realice la suma de ellos, imprima el resultado");
        problem.setDifficulty(2);
        problem.setTime(1);
        problem.setIdProblem(1111111111);
        
        Vector<TestCase> TestCases = new Vector<TestCase>();
        for (int i = 0; i < 5; i++)
        {
            TestCases.add( new TestCase() );
            TestCases.get( i ).setInput(""+(i+1)+" "+(i+2)+" "+(i+3));
            TestCases.get( i ).setOutput(""+(3*i+6));
            //TestCases.get( i ).setIdProblem( problem );
            TestCases.get( i ).setIdTestCase(1000000000+i);
            System.out.println(TestCases.get(i).toString());
        }
        User competitor = new User();
        competitor.setIdUser(10000001);
        Submission submission = new Submission();
        submission.setCode(code);
        submission.setDateTime( new Date() );
        submission.setUser(competitor);
        submission.setProblem(problem);
        submission.setIdSubmission(1000000001);
        submission.setLanguage("CPP");
        CompileAndExecuteAllTestCase cae = new CompileAndExecuteAllTestCase(problem,TestCases,competitor,submission);

        if( cae.isSuccessfullyCompiled() )
        {
            System.out.println("COMPILACION exitosa");
            System.out.println("COMPILATION OUTPUT:");
            System.out.println(cae.getCompilationOutput());
            System.out.println("EXECUTION OUTPUT:");
            System.out.println(cae.getExecutionOutput());
            cae.runTestCases();
        
            System.out.println("Runs:");
            Vector<Run> rns = cae.getRuns();
            for (int i = 0; i < rns.size(); i++) {
                Run run = rns.get(i);
                System.out.println("run# "+i+": ");
                System.out.println(run.toString());
            }
            System.out.println("VERDICT:");
            System.out.println(cae.getverdict());
            
        }
        else
        {
            System.out.println("COMPILATION ERROR");
            System.out.println(cae.compilationOutput);
        }
        // TODO ojo con esto
        CompileAndExecuteAllTestCase.killProcess("java.exe");
        CompileAndExecuteAllTestCase.killProcess("a.exe");
    }
    private String getCompilationInstruction()
    {
        if( this.languaje.equalsIgnoreCase("CPP") ) return "cmd.exe /C g++ "+EnumFileNames.CPP_SOURCE_CODE_NAME.value();
        return "cmd.exe /C javac "+EnumFileNames.JAVA_SOURCE_CODE_NAME.value();
    }
    private String getExecutionInstruction()
    {
        if( this.languaje.equalsIgnoreCase("CPP") ) return "cmd.exe /C "+EnumFileNames.EXE_FILE_NAME.value()+" < "+EnumFileNames.INPUT_FILE_NAME.value()+" > "+EnumFileNames.OUTPUT_FILE_NAME.value()+"";
        return "cmd.exe /C java "+EnumFileNames.CLASS_FILE_NAME.value()+" < "+EnumFileNames.INPUT_FILE_NAME.value()+" > "+EnumFileNames.OUTPUT_FILE_NAME.value()+"";
    }
    private boolean compile()
    {
        try
        {
            Runtime rt = Runtime.getRuntime();
            Process p=rt.exec (getCompilationInstruction());
            InputStream es = p.getErrorStream();
            BufferedReader br = new BufferedReader (new InputStreamReader (es));
            String aux;
            this.compilationOutput = "";
            while ( (aux = br.readLine())!=null )
                compilationOutput += aux+"\n";

            if( this.languaje.equals("CPP"))
            {
                File file = new File(EnumFileNames.EXE_FILE_NAME.value());
                if( !file.exists() )
                    return false;
            }
            else
            {
                File file = new File(EnumFileNames.CLASS_FILE_NAME.value()+".class");
                if( !file.exists() )
                    return false;
            }
            return true;
        }
        catch( Exception e )
        {
            e.printStackTrace();
            return false;
        }
    }
    public String getSourceCode() {
        return this.sourceCode;
    }
    public String getCompilationOutput() {
        return this.compilationOutput;
    }
    public String getExecutionOutput() {
        return this.executionOutput;
    }
    public String getverdict(){
        return this.verdict;
    }
    private void deleteFiles() {
        for (EnumFileNames o : EnumFileNames.values())
        {
            File file = new File( o.value() );
            file.delete();
        }
        File file = new File( EnumFileNames.CLASS_FILE_NAME.value()+".class" );
        file.delete();
        file = new File( "a.out" );
        file.delete();
    }
    private boolean createSourceFile()
    {
        return createFile( this.languaje.equalsIgnoreCase("CPP") ? EnumFileNames.CPP_SOURCE_CODE_NAME.value() : EnumFileNames.JAVA_SOURCE_CODE_NAME.value(), sourceCode );
    }
    private boolean createInputFile( String input )
    {
        return createFile( EnumFileNames.INPUT_FILE_NAME.value() , input );
    }
    private boolean createJudgeOutputFile( String output )
    {
        return createFile( EnumFileNames.JUDGE_OUTPUT_FILE_NAME.value() , output );
    }
    private static boolean createFile( String fileName , String content )
    {
        try
        {
            PrintWriter sourceCode = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
            sourceCode.println(content);
            sourceCode.close();
            return true;
        }
        catch (Exception e)
        {
            System.err.println("Exception in CompileAndExceuteAllTestCase.createFile("+fileName+")");
            e.printStackTrace();
            return false;
        }
    }

    public String runTestCases()
    {
        //System.out.println("en CompileAndExecuteAllTestCase al inicio");

        if( !this.compilationSuccessfull )return EnumVerdicts.CE.value();      
        this.status = EnumStatus.RUNNING.value();
        int tle = 0;
        int wa = 0;
        int re = 0;
        int ue = 0;
        int pe = 0;
        for (int i = 0; i < TestCases.size(); i++)
        {
            //System.out.println("EJECUTANDO TestCase  "+i+" ---------------------->>>>");
            String TestCaseVerdict = EnumStatus.RUNNING.value();
            TestCase TestCase = TestCases.get(i);
            if( !createInputFile(TestCase.getInput()) )
            {
                TestCaseVerdict = EnumVerdicts.UE.value();
                ++ue;
            }
            else if( !createJudgeOutputFile(TestCase.getOutput()) )
            {
                TestCaseVerdict = EnumVerdicts.UE.value();
                ++ue;
            }
            else
            {
                //System.out.println("ANTES DE this.runTestCase()");
                TestCaseVerdict = this.runTestCase();
                //System.out.println("DESPUES DE this.runTestCase()");
                if( TestCaseVerdict.equalsIgnoreCase( EnumVerdicts.TLE.value() ) )                
                   ++tle;
                else if( TestCaseVerdict.equalsIgnoreCase( EnumVerdicts.RE.value() ) )                
                    ++re;
                else if( TestCaseVerdict.equalsIgnoreCase( EnumVerdicts.WA.value() ) )
                    ++wa;
                else if( TestCaseVerdict.equalsIgnoreCase( EnumVerdicts.PE.value() ) )
                    ++pe;
                
                //System.out.println("despues de los if!!!");
                Run run = new Run();
                run.setIdRun(10000000+i);
                //run.setIdSubmission(this.submission);
                run.setSubmission(submission);                
                //run.setIdTestCase(TestCase);
                run.setTestCase(TestCase);
                if( tle == 0 && re == 0 )
                    run.setOutput( this.readCompetitorOutput() );
                else
                    run.setOutput("ERROR: "+TestCaseVerdict);
                run.setVerdict(TestCaseVerdict);
                run.setTime((int) auxTime);
                this.runs.add(run);
                if( tle + re + ue >= 2 )
                    break;
                //System.out.println(i+","+tle+","+re+","+ue+","+wa+" i,tle,re,ue,wa");
                //System.out.println("run agregado!!!"+i);
            }
        }
        this.status = EnumStatus.SUCCESSFULLY_JUDGED.value();
        this.deleteFiles();
        //System.out.println("en CompileAndExecuteAllTestCase al final");
        if( ue > 0 )
            this.verdict = EnumVerdicts.UE.value();
        else if ( re > 0 )
            this.verdict = EnumVerdicts.RE.value();
        else if ( tle > 0 )
            this.verdict = EnumVerdicts.TLE.value();
        else if ( wa > 0 )
            this.verdict = EnumVerdicts.WA.value();
        else if( pe > 0 )
            this.verdict = EnumVerdicts.PE.value();
        else
            this.verdict = EnumVerdicts.AC.value();
        //System.out.println("en CompileAndExecuteAllTestCase al final verdict = "+this.verdict);

        return this.verdict;
    }
    private String readCompetitorOutput()
    {
        String ret = "";
        Vector<String> file = CompileAndExecuteAllTestCase.readFile( EnumFileNames.OUTPUT_FILE_NAME.value() );
        for (String string : file) {
            ret+=string+"\n";
        }
        return ret;
    }
    private static String auxVerdict;
    private static long auxTime;
    private String runTestCase()
    {
        RunThread rt = new RunThread();

        int lim = 1000*this.problem.getTime()/50;
        int i;
        long inicio = System.currentTimeMillis();
        auxVerdict = "";
        rt.start();
        for (i = 0; rt.isAlive() && i < lim; i++)
        {
            try {
                Thread.sleep(50);
            } catch (Exception ex) {
                System.err.println("Exception in CompileAndExceuteAllTestCase.run()");
                ex.printStackTrace();
            }
        }
        //System.out.println("I = "+i+" LIM = "+lim);
        rt.stop();
        rt.suspend();
        proc.destroy();
        //System.out.println("DESPUES DE STOP");
        try
        {
            CompileAndExecuteAllTestCase.killProcess(this.languaje.equalsIgnoreCase("CPP") ? "a.exe" : "java.exe");
            //System.out.println("DESPUES DE killl");
        }
        catch( Exception e )
        {
            //System.out.println("Exception en CompileAndExecuteAllTestCase.runTestCase()");
            e.printStackTrace();
        }
        //System.out.println("Después del tryCatch de kill");
        auxTime = System.currentTimeMillis()-inicio;
        //System.out.println("TIEMPO DE EJECUCION: "+auxTime);
        if( i >= lim )
            return EnumVerdicts.TLE.value();


        return auxVerdict = this.compareOutputs();
    }
    private class RunThread extends Thread
    {
        private boolean continuar = true;
        public boolean getContinuar() { return continuar; }
        public void stopThread(){continuar=false;}
        @Override public void run()
        {
            auxVerdict = runn();
            this.stopThread();
        }
    }
    /**
     *
     * @return false cuando la ejecución termina de manera inesperada, true cuando termina normalmente
     */
    //Variable auxiliar para "matar" 
    private static Process proc;
    private String runn()
    {
        try
        {
            Runtime rt =Runtime.getRuntime();
            proc=rt.exec (getExecutionInstruction());

            InputStream es = proc.getErrorStream();
            BufferedReader br = new BufferedReader (new InputStreamReader (es));
            String aux;
            this.executionOutput = "";
            while ( (aux = br.readLine())!=null )
                executionOutput += aux+"\n";
            return this.compareOutputs();
        }
        catch( Exception e )
        {
            System.err.println("Exception in CompileAndExceuteAllTestCase.run()");
            e.printStackTrace();
            return EnumVerdicts.RE.value();
        }
    }
    private String compareOutputs()
    {        
        Vector<String> judge = readFile(EnumFileNames.JUDGE_OUTPUT_FILE_NAME.value());
        Vector<String> contestant = readFile(EnumFileNames.OUTPUT_FILE_NAME.value());
        //System.out.println("En CompileAndExecuteAllTestCase.compareOutputs "+judge.size()+" =? "+contestant.size());

        if( judge == null || contestant == null )
            return EnumVerdicts.UE.value();

        if( judge.size() != contestant.size() )
            return EnumVerdicts.WA.value();
        boolean AC = true;
        boolean PE = true;
        for (int i = 0; i < judge.size(); i++)
        {
            AC = AC && judge.get(i).equals(contestant.get(i));
            PE = PE && judge.get(i).trim().equals(contestant.get(i).trim());
            if( (!AC) && (!PE) )
            {
                //System.out.println("en CompileAndExecuteAllTestCase.java.compareOuputs NOT OK");
                //System.out.println("_"+judge.get(i)+"_"+contestant.get(i)+"_");
                return EnumVerdicts.WA.value();
            }
            else
            {
                //System.out.println("en CompileAndExecuteAllTestCase.java.compareOuputs OK ");
                //System.out.println("_"+judge.get(i)+"_"+contestant.get(i)+"_");
            }
        }
        //System.out.println("en CompileAndExecuteAllTestCase.java.compareOuputs AC "+AC);
        //System.out.println("en CompileAndExecuteAllTestCase.java.compareOuputs PE "+PE);

        //System.out.println("en CompileAndExecuteAllTestCase.java.compareOuputs (!AC) && PE "+((!AC) && PE));
        if( (!AC) && PE )
            return EnumVerdicts.PE.value();
        if( AC )
            return EnumVerdicts.AC.value();
        return EnumVerdicts.WA.value();
    }
    public static Vector<String> readFile( String fileName )
    {
        BufferedReader br;
        File file = new File(fileName);
        String cad;
        Vector<String> ret = new Vector<String>();
        if( file.exists() )
        {
            try
            {
                br = new BufferedReader(new FileReader(file));
                try
                {
                    while ((cad = br.readLine()) != null)ret.add(cad);
                }
                catch (Exception ex)
                {
                    System.err.println("Exception in CompileAndExceuteAllTestCase.readFile() 1");
                    ex.printStackTrace();
                    return null;
                }
            }
            catch (Exception ex)
            {
                System.err.println("Exception in CompileAndExceuteAllTestCase.readFile() 2");
                ex.printStackTrace();
                return null;
            }
        }
        else
        {
            return null;
        }
        return ret;
    }
}
